Rate Limiting
Rate limiting restricts how many requests a client can make to your server within a defined time window.
In NGINX, this is implemented using the limit_req module, which protects against:
- Brute-force login attacks
- DDoS and HTTP flood attacks
- API abuse
- Excessive bot traffic
- Resource exhaustion
How limit_req Works (Internals)
NGINX uses the leaky bucket algorithm:
- Each client (IP, user, token, etc.) has a “bucket”
- Requests add water to the bucket
- Water leaks out at a fixed rate (
rate) - If the bucket overflows → request is delayed or rejected
Key idea:
Requests are allowed at a steady rate, with controlled bursts.
Core Components of limit_req
limit_req_zone (Define the Rate Limit)
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
| Part | Meaning |
|---|---|
$binary_remote_addr | Client identifier (IP address) |
zone=api_limit:10m | Shared memory zone (10 MB) |
rate=10r/s | 10 requests per second per client |
10 MB ≈ 160,000 unique IPs
limit_req (Apply the Limit)
limit_req zone=api_limit burst=20 nodelay;
| Option | Purpose |
|---|---|
zone | Which rate limit to apply |
burst | Allow short traffic spikes |
nodelay | Reject excess immediately |
Basic Example: Global Rate Limiting
http {
limit_req_zone $binary_remote_addr zone=global:10m rate=5r/s;
server {
listen 80;
location / {
limit_req zone=global burst=10;
proxy_pass http://backend;
}
}
}
- 5 requests/sec allowed per IP
- Up to 10 extra requests queued
- Excess requests are delayed
Strict Security Example: Block Bruteforce Logins
limit_req_zone $binary_remote_addr zone=login:10m rate=1r/s;
server {
listen 443 ssl;
location /login {
limit_req zone=login burst=3 nodelay;
proxy_pass http://auth_backend;
}
}
- Max 1 request per second
- Burst of 3 attempts
- Excess requests → HTTP 503
API Rate Limiting (Recommended Pattern)
Define Limit per Client
limit_req_zone $binary_remote_addr zone=api:20m rate=20r/s;
Apply to API Endpoints
location /api/ {
limit_req zone=api burst=40 nodelay;
proxy_pass http://api_backend;
}
Result:
- 20 requests/sec
- Burst up to 40
- Hard limit beyond burst
Using Custom Keys (More Secure)
Rate Limit by API Key Header
limit_req_zone $http_x_api_key zone=api_key:10m rate=10r/s;
If header is missing → all requests share the same bucket
- Useful for authenticated APIs
- Better than IP-based limiting
Burst vs No Burst (Security Impact)
| Config | Behavior |
|---|---|
burst=0 | Hard limit |
burst=10 | Allows short spikes |
burst=10 nodelay | Reject excess immediately |
limit_req zone=api burst=10 nodelay;
Best for security-critical endpoints
Custom Error Response for Rate Limit
limit_req_status 429;
location /api/ {
limit_req zone=api burst=20;
}
Returns HTTP 429 Too Many Requests
Combining Rate Limiting with HTTPS
server {
listen 443 ssl;
ssl_certificate /path/fullchain.pem;
ssl_certificate_key /path/privkey.pem;
location /api/ {
limit_req zone=api burst=20 nodelay;
proxy_pass http://backend;
}
}
Common Security Mistakes
| Mistake | Risk |
|---|---|
Using $remote_addr instead of $binary_remote_addr | Higher memory usage |
No rate limiting on /login | Brute-force vulnerability |
Too high burst | Ineffective protection |
No limit_req_status | Poor monitoring |
| IP-only limits behind proxy | All users share one bucket |
Rate Limiting Behind Load Balancers
Fix Real Client IP
set_real_ip_from 10.0.0.0/8;
real_ip_header X-Forwarded-For;
Then use:
limit_req_zone $binary_remote_addr zone=realip:10m rate=5r/s;
Testing Rate Limiting
Using curl
for i in {1..20}; do curl -I https://example.com/login; done
Expected:
- Initial success
- Then 429 or 503 errors
limit_req vs limit_conn
| Feature | limit_req | limit_conn |
|---|---|---|
| Limits | Request rate | Concurrent connections |
| Use case | APIs, login | WebSocket, slow clients |
| Algorithm | Leaky bucket | Hard counter |